package com.storm.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.storm.cache.AuthCacheManager
import com.storm.cache.TokenValue
import com.storm.common.exception.StatusInfo
import com.storm.common.jackson.toJson
import com.storm.common.response.fail
import com.storm.common.response.success
import com.storm.component.AccessDecisionVoterCustomizer
import com.storm.component.FilterInvocationSecurityMetadataSourceCustomizer
import com.storm.model.JwtUser
import com.storm.util.StormAuthConfigProperties
import com.storm.util.inter.TokenFilterInterface
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.security.access.vote.UnanimousBased
import org.springframework.security.config.annotation.ObjectPostProcessor
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import java.time.LocalDateTime
import javax.servlet.http.Cookie


@Configuration
class SecurityConfig(
    private val jwtTokenComponent: JwtTokenComponent,
    private val userDetailsService: UserDetailsService,
    private val objectMapper: ObjectMapper,
    private val authCacheManager: AuthCacheManager,
    private val tokenFilter: TokenFilterInterface,
    private val stormAuthConfigProperties: StormAuthConfigProperties,
) : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        http
            .cors().disable()
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .anyRequest().authenticated()
            .withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
            .accessDecisionManager(UnanimousBased(listOf(AccessDecisionVoterCustomizer<Any>())))
            .and()
            .formLogin().loginProcessingUrl(stormAuthConfigProperties.loginPath)
            .successHandler(
                authenticationSuccessHandler(jwtTokenComponent, stormAuthConfigProperties, authCacheManager)
            )
            .failureHandler(
                authenticationFailureHandler()
            )
            .and()
            .addFilterBefore(
                tokenFilter,
                UsernamePasswordAuthenticationFilter::class.java
            )
    }

    override fun configure(auth: AuthenticationManagerBuilder) {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(BCryptPasswordEncoder())
    }


    /**
     * 自定义 FilterSecurityInterceptor
     * ObjectPostProcessor 以替换默认配置达到动态权限的目的
     * @return ObjectPostProcessor
     */
    private fun filterSecurityInterceptorObjectPostProcessor(): ObjectPostProcessor<FilterSecurityInterceptor> {
        return object : ObjectPostProcessor<FilterSecurityInterceptor> {
            override fun <O : FilterSecurityInterceptor> postProcess(o: O): O {
                o.securityMetadataSource = FilterInvocationSecurityMetadataSourceCustomizer(authCacheManager)
                return o
            }
        }
    }

    /**
     * 登入成功后的处理
     */
    fun authenticationSuccessHandler(
        jwtTokenComponent: JwtTokenComponent,
        stormAuthConfigProperties: StormAuthConfigProperties,
        authCacheManager: AuthCacheManager,
    ): AuthenticationSuccessHandler {
        return AuthenticationSuccessHandler {request, response, auth ->
            val channel = request.getHeader("channel") ?: "NORMAL"
            val userDetails = auth.principal
            if (userDetails is JwtUser) {
                val expirationTime = LocalDateTime.now().plusSeconds(stormAuthConfigProperties.expiration)
                val createToken = jwtTokenComponent.createToken(userDetails.username, false)
                val tokenValue = TokenValue(createToken, expirationTime, channel, userDetails.username)
                val tokenValues = authCacheManager.getTokensByUser(userDetails.username)
                if ("true" == stormAuthConfigProperties.loginAlone) {
                    authCacheManager.tokenSave(
                        userDetails.username,
                        authCacheManager.clearChannelTokens(
                            authCacheManager.clearExpirationTokens(tokenValues),
                            channel
                        ).plus(tokenValue),
                        stormAuthConfigProperties.expiration
                    )
                } else {
                    authCacheManager.tokenSave(
                        userDetails.username,
                        authCacheManager.clearExpirationTokens(tokenValues).plus(tokenValue),
                        stormAuthConfigProperties.expiration
                    )
                }
                val cookie = Cookie("token", createToken)
                cookie.isHttpOnly = true
                cookie.path = "/"
                response.addCookie(cookie)
                if ("true" != stormAuthConfigProperties.onlyCookie) {
                    response.contentType = MediaType.APPLICATION_JSON_VALUE
                    response.writer.write(success(createToken).toJson(objectMapper))
                }
            }
        }
    }

    /**
     * 登入验证失败后的处理
     */
    fun authenticationFailureHandler(): AuthenticationFailureHandler {
        return AuthenticationFailureHandler { _, response, authException ->
            response.contentType = MediaType.APPLICATION_JSON_VALUE
            response.writer.write(fail<Void>(status = StatusInfo.BUSINESS_ERROR.status(),msg = authException.message).toJson(objectMapper))
        }
    }




}
